package com.loklok.code.confuse.service;

import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.*;
import com.loklok.code.confuse.extension.FeignInterfaceInfoCache;
import com.loklok.code.confuse.model.FeignInterfaceInfo;
import com.loklok.code.confuse.util.AnnotationValueResolver;
import com.loklok.code.confuse.util.BackgroundTaskRunner;
import com.loklok.code.confuse.util.PsiFileUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

public class FeignInterfaceFinderService {

    private static final Map<String, FeignInterfaceInfo> feignInterfaceInfoMap = new ConcurrentHashMap<>();

    private static final String FEIGN_CLIENT_ANNOTATION = "org.springframework.cloud.openfeign.FeignClient";
    private static final String[] HTTP_METHOD_ANNOTATIONS = {
            "org.springframework.web.bind.annotation.GetMapping",
            "org.springframework.web.bind.annotation.PostMapping",
            "org.springframework.web.bind.annotation.PutMapping",
            "org.springframework.web.bind.annotation.DeleteMapping"
    };
    public static void findFeignClients(AnActionEvent anActionEvent) {

        Project project = anActionEvent.getProject();

        List<PsiClass> classes = getFeignClientPsiClasses(anActionEvent);
        if (classes == null) {
            return;
        }

        new BackgroundTaskRunner<PsiClass, Void>(
                project,
                "Finding Feign Clients",
                classes,
                psiClass -> {
                    ApplicationManager.getApplication().runReadAction(() -> {
                        // 原 processFeignMethods 方法内容
                        processFeignMethods(psiClass);
                    });
                    return null;
                },
                result -> { /* UI 不需要回调 */ },
                () -> {
                    // 所有完成后写入缓存
                    FeignInterfaceInfoCache instance = FeignInterfaceInfoCache.getInstance(project);
                    instance.addUpdateCache(feignInterfaceInfoMap);
                    Messages.showInfoMessage("更新 feign 元数据缓存成功", "Info");
                }
        ).run();
    }

    @Nullable
    public static List<PsiClass> getFeignClientPsiClasses(AnActionEvent anActionEvent) {
        List<PsiClass> selectedClasses = PsiFileUtils.getSelectedClasses(anActionEvent);
        if(CollectionUtils.isEmpty(selectedClasses)){
            Messages.showErrorDialog("未找到 FeignClient 接口!", "Error");
            return null;
        }

        // 遍历所有被 @FeignClient 注解的接口
        List<PsiClass> classes = StreamSupport.stream(selectedClasses.spliterator(), false)
                .filter(it -> it.hasAnnotation(FEIGN_CLIENT_ANNOTATION) && it.isInterface())
                .collect(Collectors.toList());
        if(CollectionUtils.isEmpty(classes)){
            Messages.showErrorDialog("未找到 FeignClient 接口!", "Error");
            return null;
        }
        return classes;
    }

    private static void processFeignMethods(PsiClass feignInterface) {

        PsiAnnotation[] annotations = feignInterface.getModifierList().getAnnotations();

        PsiAnnotation feignClientAnnotation = Arrays.asList(annotations).stream().filter(annotation -> FEIGN_CLIENT_ANNOTATION.equals(annotation.getQualifiedName())).findFirst().get();
        PsiAnnotationMemberValue value = feignClientAnnotation.findAttributeValue("value");
        if (value == null) {
            return;
        }

        String feignClientName = AnnotationValueResolver.getStringValue(value);
        if(StringUtils.isEmpty(feignClientName)){
            return;
        }

        String feignInterfaceName = feignInterface.getQualifiedName();

        String packageNamePrefix = null;
        PsiFile file = feignInterface.getContainingFile();
        if (file instanceof PsiJavaFile) {
            String packageName = ((PsiJavaFile) file).getPackageName();
            packageNamePrefix = packageName.substring(0, packageName.indexOf('.') + 1);
        }

        for (PsiMethod method : feignInterface.getMethods()) {
            if (method.isConstructor()) {
                continue;
            }

            FeignInterfaceInfo feignInterfaceInfo = new FeignInterfaceInfo();
            feignInterfaceInfo.setFeignClassName(feignInterfaceName);
            feignInterfaceInfo.setFeignAnnotationName(feignClientName);
            feignInterfaceInfo.setMethodName(method.getName());

            //  获取方法入参类型
            for (PsiParameter parameter : method.getParameterList().getParameters()) {
                PsiType paramType = parameter.getType();
                String canonicalText = paramType.getCanonicalText();
                List<String> genericList = PsiFileUtils.extractGenericTypes(paramType, packageNamePrefix);
                if(CollectionUtils.isNotEmpty(genericList)){
                    feignInterfaceInfo.setParameterType(genericList.get(0));
                } else if(canonicalText.startsWith(packageNamePrefix)){
                    feignInterfaceInfo.setParameterType(canonicalText);
                }
            }

            // 获取方法返回值类型
            PsiType returnType = method.getReturnType();
            if (returnType != null) {
                String canonicalText = returnType.getCanonicalText();
                feignInterfaceInfo.setReturnType(canonicalText);
                List<String> genericList = PsiFileUtils.extractGenericTypes(returnType, packageNamePrefix);
                feignInterfaceInfo.setReturnGenericTypes(genericList);
            }


            // 获取方法上的 HTTP 方法注解及 value 值
            PsiAnnotation[] methodAnnotations = method.getModifierList().getAnnotations();
            Optional<PsiAnnotation> first = Arrays.asList(methodAnnotations).stream().filter(annotation -> StringUtils.isNotEmpty(annotation.getQualifiedName())
                    && isHttpMethodAnnotation(annotation.getQualifiedName())).findFirst();

            if(!first.isPresent()){
               return;
            }
            PsiAnnotation methodAnnotation = first.get();
            feignInterfaceInfo.setMethodAnnotationName(methodAnnotation.getQualifiedName());
            PsiAnnotationMemberValue attributeValue = methodAnnotation.findAttributeValue("value");
            List<String> paths = AnnotationValueResolver.getArrayValues(attributeValue);
            if(CollectionUtils.isNotEmpty(paths)){
                paths.stream().forEach(path -> {
                    path = path.startsWith("/") ? path : "/" + path;
                    feignInterfaceInfoMap.put(path, feignInterfaceInfo);
                });
            }
        }
    }

    private static boolean isHttpMethodAnnotation(String qualifiedName) {
        for (String anno : HTTP_METHOD_ANNOTATIONS) {
            if (anno.equals(qualifiedName)) {
                return true;
            }
        }
        return false;
    }
}
